<!doctype html>
<html>
  <head>
    <title>Test Biquad Tail-Time</title>
    <script src="../../resources/testharness.js"></script>
    <script src="../../resources/testharnessreport.js"></script>
    <script src="../resources/audit-util.js"></script>
    <script src="../resources/audit.js"></script>
    <script src="../resources/biquad-filters.js"></script>
    <script src="test-tail-time.js"></script>
  </head>

  <body>
    <script>
      let audit = Audit.createTaskRunner();

      let sampleRate = 16384;
      let renderSeconds = 1;
      let renderFrames = renderSeconds * sampleRate;

      // For a peaking filter:
      //   b0 = 1 - alpha*A
      //   b1 = -2*cos(w0)
      //   b2 = 1 + alpha*A
      //   a0 = 1 + alpha/A
      //   a1 = -2*cos(w0)
      //   a2 = 1 - alpha/A
      //
      // where alpha = sin(w0)/(2*Q), w0 = 2*%pi*f0/Fs, and A = 10^(G/40)
      //
      // Equivalently a1 = -2*cos(w0)/(1+alpha/A), a2 = (1-alpha/A)/(1+alpha/A).
      // The poles of this filter are at
      //
      //   A*cos(w0)/(A + alpha) +/- sqrt(alpha^2-A^2*sin(w0)^2)/(A + alpha)
      //
      // But alpha^2-A^2*sin(w0)^2 = sin(w0)^2*(1/4/Q^2-1).
      // Thus, the poles are complex if 1/(4*Q^2) < A^2; real and distinct if
      // 1/(4*Q^2)>A^2; and repeated if 1/(4*Q^2) = A^2 or w0 = 0.

      // Array of tests to run.  |descripton| is the task description for
      // audit.define.  |parameters| is option for |testTailTime|.
      let tests = [
        {
          descripton:
              {label: 'peaking-complex-roots', description: 'complex roots'},
          parameters: {
            prefix: 'Peaking complex roots',
            // A gain of 40 gives A = 10.
            filterOptions:
                {type: 'peaking', Q: 10, gain: 40, frequency: sampleRate / 4},
            // Node computed tail frame is 2077.4 frames, which matches the
            // actual tail, so tail output should be exactly zero.
            threshold: 0
          }
        },
        {
          descripton: {
            label: 'peaking-real-distinct-roots',
            description: 'real distinct roots'
          },
          parameters: {
            prefix: 'Peaking real distinct roots',
            filterOptions: {
              type: 'peaking',
              Q: 0.001,
              gain: 40,
              frequency: sampleRate / 4
            },
            // Node computed tail frame is 588 frames, which matches the actual
            // tail, so tail output should be exactly zero.
            threshold: 0
          }
        },
        {
          descripton: {
            label: 'peaking-repeated-roots',
            description: 'repeated real root'
          },
          parameters: {
            prefix: 'Peaking repeated roots',
            filterOptions:
                {type: 'peaking', Q: 1 / 2, gain: 0, frequency: sampleRate / 8},
            // Node computed tail frame is 0 frames, which matches the actual
            // tail, so tail output should be exactly zero.
            threshold: 0

          }
        },
        {
          descripton: {
            label: 'peaking-repeated-roots 2',
            description: 'repeated real root'
          },
          parameters: {
            prefix: 'Peaking repeated roots 2',
            filterOptions: {type: 'peaking', Q: 1, gain: 40, frequency: 0},
            // Node computed tail frame is 1 frames, which matches the actual
            // tail, so tail output should be exactly zero.
            threshold: 0
          }
        }
      ];

      // Define an appropriate task for each test.
      tests.forEach(entry => {
        audit.define(entry.descripton, (task, should) => {
          let context = new OfflineAudioContext(1, renderFrames, sampleRate);
          testTailTime(should, context, entry.parameters).then(() => task.done());
        });
      });

      audit.define(
          {label: 'peaking-unstable', description: 'Roots on unit circle'},
          async (task, should) => {
            let context = new OfflineAudioContext(
                {length: renderFrames, sampleRate: sampleRate});
            let src = new OscillatorNode(context);
            // See https://crbug.com/1172332 for the values here.
            let f = new BiquadFilterNode(context, {
              type: 'peaking',
              frequency: 27,
              Q: 2993.82007,
              gain: 1340,
              detune: -145229
            });
            src.connect(f).connect(context.destination);
            src.start();

            let buffer = await context.startRendering();

            // This is testing that a DCHECK doesn't happen, so the
            // results aren't important.
            should(true, 'Rendering finished successfully').beTrue();
            task.done();
          });

      audit.run();
    </script>
  </body>
</html>
